#!/usr/bin/python
# -*- coding: latin1 -*-
"""

    Name: _musicXMLbase.py

    Author: Enrico Bortolazzi - Veia Progetti s.r.l.

    Comment: musicXML base classes

    Version: 1.0

    Date: Fri Aug 17, 2007

------------------------------------------------------------------------

Copyright (c) 2007, Veia Progetti s.r.l. - http://www.veia.it

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.

    Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the distribution.

    Neither name of Veia Progetti s.r.l. nor the name of its contributors
    may be used to endorse or promote products derived from this software
    without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""

from _musicXML import *

_version = 2.0 #musicXML DTD version

class _Element:
    """ musicXML element base class """
    _name = ''
    _attrs = []
    _elements = []
    _has_content = False

    def __init__(self):
        self._data = []

    def __iter__(self): return _ElementIterator(self)

    def _set_element(self, name, item):
        if item._name != name:
            raise ValueError, '_set_element: class name = %s but passed name %s'\
                % (item._name, name)
        d = self._get_element(name)
        if d != None:
            self._data.remove(d)
        self._data.append(item)

    def _get_element(self, name):
        items = [d for d in self._data if d._name == name]
        if not items: return None
        return items[0]

    def _get_element_ls(self, name):
        items = [d for d in self._data if d._name == name]
        return items

    def _add_element(self, name, item):
        if item._name != name:
            raise ValueError, '_add_element: class name = %s but passed name %s'\
                % (item._name, name)
        self._data.append(item)

    def _del_element(self, name, item=None):
        # if there is more then one instance for itme only the first is deleted
        if item == None:
            item = self._get_element(name)
            if isinstance(item, list):
                raise ValueError, '_del_element: more then one item with name %s' %name
        elif item._name != name:
            raise ValueError, '_del_element: class name = %s but passed name %s'\
                % (item._name, name)
        self._data.remove(item)

    def _insert_element(self, name, item, next_item):
        if item == None: return
        if item._name != name:
            raise ValueError, '_insert_element: class name = %s but passed name %s'\
                % (item._name, name)
        if next_item != None:
            if next_item._name != name:
                raise ValueError, '_insert_element: class name = %s but passed name %s'\
                    % (next_item._name, name)
            i = self._data.index(next_item)
        else:
            i = len(self._data)
        self._data.insert(i, item)

    def add(self, item):
        """ add an element as child of the object.
            It uses the add_xx function or the set_xx function (removing previous value if present)
        """
        name = name_mangle(item._name)
        f = getattr(self, 'add_'+name, None)
        if not f:
            f = getattr(self, 'set_'+name)
        f(item)

    def remove(self, item):
        """ remove an element as child of the object """
        name = name_mangle(item._name)
        f = getattr(self, 'del_'+name)
        f(item)


class _SequenceIterator:
    """
    simple iterator over element._data
    """
    def __init__(self, element):
        self.iter = iter(element._data)
    def __iter__(self):
        return self
    def next(self):
        return self.iter.next()


class _ElementIterator:
    """
    iterator over element._elements
    """
    def __init__(self, element):
        self.element = element
        self.elements_iter = iter(element._elements)
        self.data_iter = None
        self.last = None
    def __iter__(self):
        return self
    def next(self):
        if not len(self.element._data):
            raise StopIteration
        if self.data_iter != None:
            try:
                return self.data_iter.next()
            except StopIteration:
                self.data_iter = None
        self.last = self.elements_iter.next()
        data = self.element._get_element_ls(self.last)
        while not data:
            self.last = self.elements_iter.next()
            data = self.element._get_element_ls(self.last)

        if len(data) == 1: return data[0]

        self.data_iter = iter(data)
        return self.data_iter.next()


class _SortIterator:
    """ sort helper class """
    def __init__(self, element):
        self.element = element
        self.sorted_data = []
        self.sort()
        self.iter = iter(self.sorted_data)
    def sort(self):
        raise NotImplementedError, '_SortIterator must be inherited'
    def sort_item(self, item):
        i = getattr(self.element, 'get_%s' % name_mangle(item))()
        if i:
            if isinstance(i, list):
                self.sorted_data.extend(i)
            else:
                self.sorted_data.append(i)
            return True
        return False


class _NoteIterator(_SortIterator):
    """
    note iterator
    """
    def __iter__(self):
        return self
    def next(self):
        return self.iter.next()
    def sort(self):
        """ order elements according to dtd rule """
        grace = self.sort_item('grace')
        if not grace:
            cue = self.sort_item('cue')
        else:
            cue = False
        self.sort_item('chord')
        pitch = self.sort_item('pitch')
        if not pitch:
            unpitched = self.sort_item('unpitched')
            if not unpitched:
                self.sort_item('rest')
        if not grace:
            self.sort_item('duration')
        if not cue:
            self.sort_item('tie')
        for i in self.element._elements[8:]:
            self.sort_item(i)


class _MetronomeIterator(_SortIterator):
    """
    metronome iterator
    """
    def __iter__(self):
        return self
    def next(self):
        return self.iter.next()
    def sort(self):
        """ order elements according to dtd rule """
        self.sort_item('_metronome')
        if _version == 2:
            if not len(self.sorted_data): return
            note = getattr(self.sorted_data[0], 'get_metronome_note')()
            if note:
                relation = self.sort_item('metronome-relation')
                if relation and len(self.sorted_data) > 2:
                    self.sorted_data = [self.sorted_data[0]] + [self.sorted_data[-1]] + [self.sorted_data[2]]
                return
        if len(self.sorted_data) == 1:
            self.sort_item('per_minute')
        elif len(self.sorted_data) > 2:
            self.sorted_data = self.sorted_data[:2]


class _HarmonyIterator_(_SortIterator):
    """
    metronome iterator
    """
    def __iter__(self):
        return self
    def next(self):
        return self.iter.next()
    def sort(self):
        """ order elements according to dtd rule """
        root = self.sort_item('root')
        if not root:
            self.sort_item('function')
        for i in self.element._elements[2:]:
            self.sort_item(i)


class _KeyIterator(_SortIterator):
    """
    key iterator
    """
    def __iter__(self):
        return self
    def next(self):
        return self.iter.next()
    def sort(self):
        """ order elements according to dtd rule """
        _key = self.sort_item('_key')
        if not _key:
            self.sort_item('cancel')
            self.sort_item('fifths')
            self.sort_item('mode')
        if _version == 2:
            self.sort_item('key-octave')


class _TimeIterator(_SortIterator):
    """
    Time iterator
    """
    def __iter__(self):
        return self
    def next(self):
        return self.iter.next()
    def sort(self):
        """ order elements according to dtd rule """
        _time = self.sort_item('_time')
        if not _time:
            for i in self.element._elements[1:]:
                self.sort_item(i)


class _OrIterator:
    """
    or iterator
    """
    def __init__(self, element):
        self.iter = iter(element._data)
        self.stop = False
    def __iter__(self):
        return self
    def next(self):
        if self.stop:
            raise StopIteration
        self.stop = True
        return self.iter.next()


class _OrnamentIterator_:
    """
    _Ornament iterator
    """
    def __init__(self, element):
        self.iter = iter(element._data)
        self.accidental_mark_iter = iter(getattr(element, 'get_accidental_mark')())
        self.first = False
    def __iter__(self):
        return self
    def next(self):
        if self.first:
            return self.accidental_mark_iter.next()
        self.first = True
        return self.iter.next()

